#pragma once
#include <cstdio>
#include <pthread.h>
#include <vector>
#include <cstdlib>
#include "../object_repres/go.h"
#include "../object_repres/boxing2.h"
#include <typeinfo>
#include <unordered_map>

extern pthread_mutex_t mut;
extern std::vector <void *> offsets;
extern bool new_active;
extern std::vector <void *> ptr_in_heap;
extern std::unordered_map <size_t, void *> list_meta_obj;
extern std::unordered_map <size_t, void *>::iterator got;
extern size_t counter;

using std::make_pair;

class base_meta
{
	public:
	void *shell;
	bool type;
	size_t size;
	
	virtual void* get_shell ()
	{
		return shell;
	}
	virtual void del_ptr () = 0;
};

template <class T> class meta : public base_meta
{
	public:
	T* ptr; 
	void del_ptr ()
	{
		if (type == 0)
		{
			((T*)ptr)->~T();
//			delete ptr;
		}
		else
		{
			for (size_t i = 0; i < size / sizeof(T); i++)
			  	((T*)ptr)[i].~T();
//			delete ptr;
		}
	}
	void* get_shell ()
	{
		return shell;
	}

};
template <class T> T* gc_new (size_t count=1)
{
	//while (pthread_mutex_trylock(&mut)!=0){};
	offsets.clear();

	counter += sizeof(T);
	if (counter >= 100000000)
	{
		mark_and_sweep();
		counter = 0;
	}
	void *res;

	if (count <= 0)
		return 0;
	new_active = true;
	offsets.clear();
	got = list_meta_obj.find(typeid(T).hash_code());
	if (count == 1)
	{

		res = malloc(sizeof(T) + sizeof(void*));
		void* tmp = res + sizeof(void*);
		new (res + sizeof(void*)) T;
		*((long*)res) =  reinterpret_cast <long> (new meta<T>);
		meta<T>* m_inf = reinterpret_cast <meta<T>* > (*((long*)res));	
		if (offsets.size() == 0)
		{
			if ( got != list_meta_obj.end() )
				m_inf->shell = got->second;
			else {
				m_inf->shell = generic_box_simple ();
				list_meta_obj.insert(make_pair(typeid(T).hash_code(),m_inf->shell));
			}
		}
		else
		{
			std::list <size_t> offsets_ptr;
			for (size_t i = 0; i < offsets.size(); i++)
			{
				offsets_ptr.push_front(reinterpret_cast <long> (offsets[i]) - reinterpret_cast <long> (tmp));
			}
			if ( got != list_meta_obj.end() )
				m_inf->shell = got->second;
			else 
			{
				m_inf->shell = generic_box_struct (offsets_ptr);
				list_meta_obj.insert(make_pair(typeid(T).hash_code(),m_inf->shell));
			}
		}
		m_inf->type = 0;
		m_inf->ptr = (T*)(res + sizeof(base_meta*));
		m_inf->size = sizeof(T);
	}
	else
	{
		res = malloc(sizeof(T) * count + sizeof(void *));
		void* tmp = res + sizeof(void*);
		new (res + sizeof(void*)) T[count];

		*((long*)res) = reinterpret_cast <long> (new meta<T>);
		meta<T> *m_inf = reinterpret_cast <meta<T> *> (*((long*)res));

		if (offsets.size() == 0)
		{
			if ( got != list_meta_obj.end() )
				m_inf->shell = got->second;
			else {
				m_inf->shell = generic_box_unboxed_array (count);
				list_meta_obj.insert(make_pair(typeid(T).hash_code(),m_inf->shell));
			}
		}
		else
		{
			std::list <size_t> offsets_ptr;
			for (size_t i = 0; i < offsets.size(); i++)
				offsets_ptr.push_front(reinterpret_cast <long> (offsets[i]) - reinterpret_cast <long> (tmp));
			if ( got != list_meta_obj.end() )
			m_inf->shell = got->second;
			else {
				m_inf->shell = generic_box_struct (offsets_ptr);
				list_meta_obj.insert(make_pair(typeid(T).hash_code(),m_inf->shell));
			}
		}		
		m_inf->type = 1;
		m_inf->ptr = (T*)(res + sizeof(base_meta*));
		m_inf->size = sizeof(T) * count;
	}

	ptr_in_heap.push_back(res + sizeof(base_meta*));
	new_active = false;
	//pthread_mutex_unlock(&mut);

	return (T*)(res + sizeof(base_meta*));
}
